package uz.rt.utils;

/**
 * <p>The class defines a volume element and its parameters. Parameters defining a voxel are:
 * <ul>
 * <li>center coordinates</li>
 * <li>dimension sizes</li>
 * </ul>
 * </p>
 * <p>
 * The class implements a Comparable interface. Comparison is done by analysing:
 * <ul>
 * <li>center coordinates</li>
 * <li>dimensions sizes</li>
 * <li>voxel value</li>
 * </ul>
 * </p>
 *
 * @param <C> Datatype used to define the center of the coordinates
 * @param <D> Datatype used to define the size of the dimensions
 * @param <V> Datatype containing a value for the whole voxel
 */
public class Voxel<C extends Comparable<? super C>,
        D extends Comparable<? super D>,
        V extends Comparable<? super V>>
        implements Comparable<Voxel<C, D, V>>
{
// ------------------------------ FIELDS ------------------------------

    /** Coordinate in 3D to defines the center of the voxel */
    private Coordinate<C> center = new Coordinate<C>(3);
    /** Dimension in 3D to defines the size of voxel */
    private Dimension<D> dimensions = new Dimension<D>(3);
    /** Value stored in the voxel */
    private V value = null;

// --------------------------- CONSTRUCTORS ---------------------------

    /** Constructor intialises the class but thus not any value */
    public Voxel()
    {

    }

    /**
     * Constructor intialises the voxel and sets the center coordinate.
     *
     * @param center Coordinate to the center of the voxel
     * @throws Exception An exception is thrown when the dimensions of the center coordinate differs from 3.
     */
    public Voxel(Coordinate<C> center) throws Exception
    {
        setCenter(center);
    }

    /**
     * Set the center coordinate of the voxel
     *
     * @param center Coordinate to the center of the voxel
     * @throws Exception An exception is thrown when the size of the center coordinate differs from 3.
     */
    public void setCenter(Coordinate<C> center) throws Exception
    {
        if (center != null)
        {
            if (center.size() != 3) throw new Exception("Center coordinate must be defined by 3 parameters");
            this.center = center;
        }
    }

    /**
     * Set the center coordinate of the voxel
     *
     * @param center    Coordinate to the center of the voxel
     * @param dimension Dimensions that determine the size of the voxel
     * @throws Exception An exception is thrown when the size of the center coordinate or dimension differs from 3.
     */
    public Voxel(Coordinate<C> center, Dimension<D> dimension) throws Exception
    {
        setCenter(center);
        setDimensions(dimension);
    }

    /**
     * Set the dimensions that determine the size of the voxel
     *
     * @param dimensions Dimensions that determine the size of the voxel
     * @throws Exception An exception is thrown when the size of the dimension differs from 3.
     */
    public void setDimensions(Dimension<D> dimensions) throws Exception
    {
        if (dimensions != null)
        {
            if (dimensions.size() != 3) throw new Exception("Center dimensions must be defined by 3 parameters");
            this.dimensions = dimensions;
        }
    }

    /**
     * Set the center coordinate of the voxel
     *
     * @param center    Coordinate to the center of the voxel
     * @param dimension Dimensions that determine the size of the voxel
     * @param value     Value stored in the voxel
     * @throws Exception An exception is thrown when the size of the center coordinate or dimension differs from 3.
     */
    public Voxel(Coordinate<C> center, Dimension<D> dimension, V value) throws Exception
    {
        setCenter(center);
        setDimensions(dimension);
        setValue(value);
    }

// --------------------- GETTER / SETTER METHODS ---------------------

    /**
     * Get the coordinates to the center of the voxel
     *
     * @return Coordinate to the center of the voxel
     */
    public Coordinate<C> getCenter()
    {
        return center;
    }

    /**
     * Get the dimensions determining the size of the voxel
     *
     * @return Dimensions that determine the size of the voxel
     */
    public Dimension<D> getDimensions()
    {
        return dimensions;
    }

    /**
     * Get the value of the voxel
     *
     * @return Value of the voxel
     */
    public V getValue()
    {
        return value;
    }

    /**
     * Set the value of the voxel
     *
     * @param value Value of the voxel
     */
    public void setValue(V value)
    {
        this.value = value;
    }

// ------------------------ CANONICAL METHODS ------------------------

    @Override
    public boolean equals(Object o)
    {
        if (this == o) return true;
        if (!(o instanceof Voxel)) return false;

        Voxel voxel = (Voxel) o;

        return !(center != null ? !center
                .equals(voxel.center) : voxel.center != null) && !(dimensions != null ? !dimensions
                .equals(voxel.dimensions) : voxel.dimensions != null) && !(value != null ? !value
                .equals(voxel.value) : voxel.value != null);
    }

    @Override
    public int hashCode()
    {
        int result = center != null ? center.hashCode() : 0;
        result = 31 * result + (dimensions != null ? dimensions.hashCode() : 0);
        result = 31 * result + (value != null ? value.hashCode() : 0);
        return result;
    }

    @Override
    public String toString()
    {
        String ln = System.getProperty("line.separator");
        StringBuilder sb = new StringBuilder();
        sb.append("Coordinates:").append(ln);
        if (this.center != null)
            sb.append(center.toString()).append(ln);
        sb.append("Dimension:").append(ln);
        if (this.dimensions != null)
            sb.append(dimensions.toString()).append(ln);
        sb.append("Value:").append(ln);
        if (value != null)
            sb.append(value.toString()).append(ln);
        return sb.toString();
    }

// ------------------------ INTERFACE METHODS ------------------------


// --------------------- Interface Comparable ---------------------

    public int compareTo(Voxel<C, D, V> o)
    {
        if (this.center != o.center) return center.compareTo(o.center);
        if (this.dimensions != o.dimensions) return dimensions.compareTo(o.dimensions);
        return this.value.compareTo(o.value);
    }
}
